﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Resources;
using System.Xml.Linq;
using System.Net;

using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Security;
using System.IO;
using System.Collections;
using System.Threading;
using System.Globalization;
using System.Threading.Tasks;

using King.Reflection;
using King.Declarative;
using King.Extensions;
using King.Text;
using King.Text.Xml;
using King.Collections;
using King.Rest;
using King.Atom;
using King.CSharp.Reflection;
using King.Rest.Xml.CSharp;
using King.Rest.Xml.CSharp.Reflection;
using King.Xml.CSharp;
using King.Xml.Reflection;

namespace King.Azure.Rest {

    internal sealed class AzureFormatter : Formatter {
        protected override string Format(string format, object value) {
            if (format == "HeaderName")
                return new TextJoin('-') { 
                    from o in value.ToString().SplitCamelCase()
                    select o.ToLowerInvariant()
                }.ReadToEnd();

            if (format == "ToUpperFirst")
                return value.ToString().ToUpperFirst();

            return null;
        }
    }

    internal static class AzureFormat {
        internal const string HeaderFormat = "x-ms-{0:HeaderName}";
    }

    //  AzureRestApi
    //      AzureSubscriptionManagement
    //          AzureStorageAccount
    //              AzureStorageKey
    //          AzureServiceAccount
    //          AzureAffinityGroup

    internal class X64Attribute : Attribute {
    }

    // *.core.windows.net
    [RestHost("core.windows.net"), RestFormatter(typeof(AzureFormatter))]
    public abstract class AzureRestApi {

        internal const string Namespace = "http://schemas.microsoft.com/windowsazure";
        internal static readonly XNamespace XNamespace = (XNamespace)Namespace;

        internal static RestXmlCSharpLoader RestXmlCSharpLoader {
            get { return s_restXmlCSharpLoader; }
        }

        private static RestXmlCSharpLoader s_restXmlCSharpLoader;

        static AzureRestApi() {

            s_restXmlCSharpLoader = 
                new RestXmlCSharpLoader(
                    CSharpLoader.Create()
                );
        }

        protected HttpWebRequest ConstructRequest(
            object context,
            IEnumerable<object> arguments,
            out object body) {

            var method = new StackTrace(false).Methods(typeof(RestRequestAttribute)).First();
            var request = RestRequest.Construct(method, context, arguments, out body);
            return request;
        }
    }

    internal class Template : TextDeclaration {

        [RestHeader]
        internal const string ContentType = "application/xml";

        private string m_root;
        private IEnumerable<RestArgument> m_content;

        internal Template(IEnumerable<RestArgument> arguments) {
            m_root = (string)arguments.Where(o => o.Target is MethodInfo).Single().Name;
            m_content = arguments.Where(o => !(o.Target is MethodInfo));
        }

        protected override IEnumerable Children(DeclarationContext frame) {
            // xml payload
            var xml = new XElement(AzureRestApi.XNamespace + m_root, 
                    
                // parameter/argument
                from argument in m_content
                let value = 
                    // x64 encoding
                    argument.IsDefined<X64Attribute>() ? 
                        Convert.ToBase64String(Encoding.UTF8.GetBytes(argument.Value.ToString())) : 
                        argument.Value.ToString()
                    
                // select element
                select new XElement(AzureRestApi.XNamespace + argument.Name.ToUpperFirst(), 
                    value == null ? null : value
                )
            );

            var xmlToString = xml.ToString();

            return new TextSingleSpaced {
                "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>",
                xmlToString
            };
        }
    }

    // management.core.windows.net
    [RestProtocol(Https = true), RestHost("management"), RestTemplate(typeof(Template))]
    public sealed class AzureSubscriptionManagement : AzureRestApi {
        internal const string DateTimeFormat = "{0:yyyy-MM-ddTHH:mm:ssZ}";

        [RestHeader(AzureFormat.HeaderFormat)]
        private const string Version = "2012-03-01";

        private string m_id;
        private X509Certificate m_certificate;

        public AzureSubscriptionManagement(string id, X509Certificate certificate) {
            m_id = id;
            m_certificate = certificate;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return InvokeWithContext(this, arguments);
        }

        internal HttpWebResponse InvokeWithContext(object context, params object[] arguments) {
            object body;
            var request = ConstructRequest(context, arguments, out body);

            if (body != null) {
                var text = (TextDeclaration)body;
                var requestStream = request.GetRequestStream();
                text.ToTextEncoder().CopyTo(requestStream);
            }

            return (HttpWebResponse)request.GetResponse();
        }

        [RestPath]
        public string Id {
            get { return m_id; }
        }

        [RestCertificate]
        public X509Certificate Certificate {
            get { return m_certificate; }
        }

        // subscription
        [RestGet]
        public HttpWebResponse Subscriptions() {
            return Invoke();
        }

        [RestGet, RestPath("operations")]
        public HttpWebResponse Logs(
            [RestQuery(null, DateTimeFormat)]DateTime startTime,
            [RestQuery(null, DateTimeFormat)]DateTime endTime,
            [RestQuery]string objectFilterId = null,
            [RestQuery]AzureLogResult? operationResultFilter = null) {
            return Invoke(startTime, endTime, objectFilterId, operationResultFilter);
        }

        // locations
        [RestGet, RestPath("locations")]
        public HttpWebResponse Locations() {
            return Invoke();
        }

        // certificates
        public AzureCertificate GetCertificate(string thumbprint) {
            return new AzureCertificate(this, thumbprint);
        }
        
        [RestGet, RestPath("certificates")]
        public HttpWebResponse Certificates() {
            return Invoke();
        }

        [RestPost, RestPath("certificates"), RestContent("SubscriptionCertificate")]
        public HttpWebResponse CreateCertificate(
            [RestContent]string subscriptionCertificatePublicKey,
            [RestContent]string subscriptionCertificateThumbprint,
            [RestContent]string subscriptionCertificateData) {

            return Invoke(
                subscriptionCertificatePublicKey,
                subscriptionCertificateThumbprint,
                subscriptionCertificateData
            );
        }

        // operating systems
        [RestGet, RestPath("operatingsystemfamilies")]
        public IEnumeratorAsync<Az.OperatingSystemFamily> OperatingSystems() {
            var result = RestXmlCSharpLoader.Deserialize(
                new StreamReader(Invoke().GetResponseStream())
                //root: XNamespace + "OperatingSystemFamilies"
            ).Result;

            var operatingSystemFamilies = ((IEnumerable<object>)result.OperatingSystemFamilies)
                .Select(o => new Az.OperatingSystemFamily(o));
            var enumerator = operatingSystemFamilies.GetEnumerator();
            enumerator.MoveNext();
            var osf = enumerator.Current;
            var os = osf.OperatingSystems().First();

            enumerator.MoveNext();
            osf = enumerator.Current;

            throw new NotImplementedException();
            //return result.Select(o => new AzureOperatingSystemFamily(o));
        }

        // storage
        public AzureStorageAccount GetStorageAccount(string name) {
            return new AzureStorageAccount(this, name);
        }

        [RestGet, RestPath("services/storageservices")]
        public HttpWebResponse StorageAccounts() {
            return Invoke();
        }

        [RestPost, RestPath("services/storageservices"), RestContent("CreateStorageServiceInput")]
        public HttpWebResponse CreateStorageAccount(
            [RestContent]string serviceName,
            [RestContent]string description,
            [RestContent, X64]string label = null,
            [RestContent]string affinityGroup = null,
            [RestContent]string location = null) {
            return Invoke(serviceName, description, label, affinityGroup, location);
        }

        // service
        public AzureServiceAccount GetServiceAccount(string name) {
            return new AzureServiceAccount(this, name);
        }

        [RestGet, RestPath("services/hostedservices")]
        public HttpWebResponse ServiceAccounts() {
            return Invoke();
        }

        [RestPost, RestPath("services/hostedservices"), RestContent("CreateHostedService")]
        public HttpWebResponse CreateServiceAccount(
            [RestContent]string serviceName,
            [RestContent, X64]string label,
            [RestContent]string description = null,
            [RestContent]string location = null,
            [RestContent]string affinityGroup = null) {
            return Invoke(serviceName, label, description, location, affinityGroup);
        }

        // affinity groups
        public AzureAffinityGroup GetAffinityGroup(string name) {
            return new AzureAffinityGroup(this, name);
        }

        [RestGet, RestPath("affinityGroups")]
        public HttpWebResponse AffinityGroups() {
            return Invoke();
        }

        [RestPost, RestPath("affinityGroups"), RestContent("CreateAffinityGroup")]
        public HttpWebResponse CreateAffinityGroup(
            [RestContent]string name,
            [RestContent, X64]string label,
            [RestContent]string description = null,
            [RestContent]string location = null) {
            return Invoke(name, label, description, location);
        }
    }
    [RestPath("certificates"), RestTemplate(typeof(Template))]
    public sealed class AzureCertificate {

        private AzureSubscriptionManagement m_credentials;
        private string m_thumbprint;

        internal AzureCertificate(AzureSubscriptionManagement credentials, string thumbprint) {
            m_credentials = credentials;
            m_thumbprint = thumbprint;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            if (m_thumbprint == null)
                throw new InvalidOperationException();

            return m_credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureSubscriptionManagement Credentials {
            get { return m_credentials; }
        }

        [RestPath]
        public string Thumbprint {
            get { return m_thumbprint; }
        }

        [RestDelete]
        public HttpWebResponse Delete() {
            return Invoke();
        }

        [RestGet]
        public HttpWebResponse Properties() {
            return Invoke();
        }
    }
    [RestPath("services/storageservices"), RestTemplate(typeof(Template))]
    public sealed class AzureStorageAccount {

        private AzureSubscriptionManagement m_credentials;
        private string m_name;

        internal AzureStorageAccount(AzureSubscriptionManagement credentials, string name) {
            m_credentials = credentials;
            m_name = name;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return m_credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureSubscriptionManagement Credentials {
            get { return m_credentials; }
        }

        [RestPath]
        public string Name {
            get { return m_name; }
        }

        // keys
        public AzureStorageKey GetKey(AzureStorageKeyType type) {
            return new AzureStorageKey(this, type);
        }

        [RestGet, RestPath("keys")]
        public HttpWebResponse Keys() {
            return Invoke();
        }

        [RestPut, RestContent("UpdateStorageServiceInput")]
        public HttpWebResponse Update(
            [RestContent]string description = null,
            [RestContent, X64]string label = null) {
            return Invoke(description, label);
        }

        [RestDelete]
        public HttpWebResponse Delete() {
            return Invoke();
        }

        [RestGet]
        public HttpWebResponse Properties() {
            return Invoke();
        }
    }
    [RestPath("keys")]
    public sealed class AzureStorageKey {
        private AzureStorageAccount m_storage;
        private AzureStorageKeyType m_type;

        internal AzureStorageKey(AzureStorageAccount storage, AzureStorageKeyType type) {
            m_storage = storage;
            m_type = type;
        }
        private HttpWebResponse Invoke(params object[] arguments) {
            return m_storage.Credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureStorageAccount Storage {
            get { return m_storage; }
        }

        [RestContent]
        public AzureStorageKeyType KeyType {
            get { return m_type; }
        }

        [RestPost, RestQuery("action", "regenerate"), RestContent("RegenerateKeys")]
        public HttpWebResponse Regenerate() {
            return Invoke();
        }
    }
    [RestPath("services/hostedservices")]
    public sealed class AzureServiceAccount {
        private AzureSubscriptionManagement m_credentials;
        private string m_name;

        internal AzureServiceAccount(AzureSubscriptionManagement credentials, string name) {
            m_credentials = credentials;
            m_name = name;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            if (m_name == null)
                throw new InvalidOperationException();

            return m_credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureSubscriptionManagement Credentials {
            get { return m_credentials; }
        }

        [RestPath]
        public string Name {
            get { return m_name; }
        }

        [RestDelete]
        public HttpWebResponse Delete() {
            return Invoke();
        }

        [RestPut, RestContent("UpdateHostedService")]
        public HttpWebResponse Update(
            [RestContent, X64]string label,
            [RestContent]string description = null) {
            return Invoke(label, description);
        }

        [RestGet]
        public HttpWebResponse Properties() {
            return Invoke();
        }
    }
    [RestPath("affinityGroups")]
    public sealed class AzureAffinityGroup {
        private AzureSubscriptionManagement m_credentials;
        private string m_name;

        internal AzureAffinityGroup(AzureSubscriptionManagement credentials, string name) {
            m_credentials = credentials;
            m_name = name;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return m_credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureSubscriptionManagement Credentials {
            get { return m_credentials; }
        }

        [RestPath]
        public string Name {
            get { return m_name; }
        }

        [RestGet]
        public HttpWebResponse Properties() {
            return Invoke();
        }

        [RestDelete]
        public HttpWebResponse AffinityGroupDelete() {
            return Invoke();
        }

        [RestPut, RestContent("UpdateAffinityGroup")]
        public HttpWebResponse AffinityGroupUpdate(
            [RestContent, X64]string label,
            [RestContent]string description = null) {
            return Invoke(label, description);
        }
    }

    [XsSchema(Namespace, 
        NameFormat = XsNameFormat.PreserveName, 
        ElementFormDefault = XsForm.Qualified
    )]
    public partial class Az {
        internal const string Namespace = "http://schemas.microsoft.com/windowsazure";
        internal static readonly XNamespace XNamespace = (XNamespace)Namespace;

        [XsSimpleType(AliasFor = typeof(string))]
        public class String : Xs.String {
        }

        [XsSimpleType(AliasFor = typeof(bool))]
        public class Boolean : Xs.Boolean {
        }

        [RestXmlMessage]
        private IEnumerable<OperatingSystemFamily> OperatingSystemFamilies { get; set; }

        [XsEnumType(BaseType = typeof(Xs.Int))]
        public enum OperatingSystemFamilyType {
            WindowsServer2008SP2 = 1,
            WindowsServer2008R2 = 2,
            WindowsServer2012 = 3,
        } 

        [RestXmlComplexType]
        public sealed class OperatingSystemFamily {
            /*
              <OperatingSystemFamily>
                <Name>2</Name>
                <Label>V2luZG93cyBTZXJ2ZXIgMjAwOCBSMg==</Label>
                <OperatingSystems>
                  ...
            */
            private dynamic m_xml;

            internal OperatingSystemFamily(dynamic xml) {
                m_xml = xml;
            }

            [RestXmlProperty(1, Name = "Name")]
            public OperatingSystemFamilyType Type {
                get { return m_xml.Name; }
            }
        
            [RestXmlProperty(2)]
            public string Label {
                get { return m_xml.Label; }
            }

            [RestXmlProperty(3)]
            public IEnumerable<OperatingSystem> OperatingSystems() {
                IEnumerable<dynamic> result = m_xml.OperatingSystems;
                return result.Select(o => new OperatingSystem(o));
            }
        }

        [RestXmlComplexType]
        public sealed class OperatingSystem {
            /*
              <OperatingSystem>
                <Version>WA-GUEST-OS-2.0_201010-01</Version>
                <Label>V2luZG93cyBBenVyZSBHdWVzdCBPUyAyLjAgKFJlbGVhc2UgMjAxMDEwLTAxKQ==</Label>
                <IsDefault>false</IsDefault>
                <IsActive>true</IsActive>
              </OperatingSystem>
            */
            private dynamic m_xml;

            internal OperatingSystem(dynamic xml) {
                m_xml = xml;
            }

            [RestXmlProperty(1)]
            public string Version {
                get { return m_xml.Version; }
            }

            [RestXmlProperty(2)]
            public string Label {
                get { return m_xml.Label; }
            }

            [RestXmlProperty(3)]
            public bool IsDefault {
                get { return m_xml.IsDefault; }
            }

            [RestXmlProperty(4)]
            public bool IsActive {
                get { return m_xml.IsActive; }
            }
        }
    }
}
